/* test framework code
   ./mainX [bitsize of operands] [number of tests] [base to print]
   defaults are 3200, 100, and 0
   0 base means no printing.  2 base is binary, 10 base is decimal, 32
   is hex.  1 base is an error.  printing is of the randomly generated
   operands and the multiplication results from the code and GMP.  A
   summary of the run is always printed.

   Half of the time, both operands are roughly the given bitsize
   (randomnly having leading bitz be zero can make them slightly
   smaller.)  The other half, one operand or the other has a random
   bitlength, thus usually being notably shorter.
   */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <gmp.h>
#include <unistd.h>
#include <time.h>

#define WORDBYTES 4 // bytes per word
#define WORDBITS 32 // bits per word

//mpz import/export parameters
#define NAILS 0 //bit padding, usually 0
#define ORDER 1 //most significant word first, -1 for least first
#define ENDIAN 0 // native endianness

extern Product32(void *a, void *b, void *c, unsigned int sa, unsigned int sb, unsigned int *sc);
int n18b = 0, n18l =0, n52 =0;

double mytime (void)
{
  int flag;
  clockid_t cid = CLOCK_REALTIME; // CLOCK_MONOTONE might be better
  struct timespec tp;
  double timing;

  flag = clock_gettime(cid, &tp);
  if (flag == 0) timing = tp.tv_sec + 1.0e-9*tp.tv_nsec;
  else           timing = -17.0;

    return(timing);
};

int main(int argc, char **argv) {

  //operands
    mpz_t x; mpz_init(x);
    mpz_t y; mpz_init(y);
    mpz_t xsize_z; mpz_init(xsize_z); //for random number generation
    mpz_t ysize_z; mpz_init(ysize_z); //for random number generation

    //GMP random state
    gmp_randstate_t randstate; gmp_randinit_default(randstate);
    gmp_randseed_ui(randstate, time(NULL));

    srand(time(NULL)); //for non-GMP random numbers

    int number_of_bits = 3200;
    int xbits, ybits;
    int number_of_tests = 100;
    int printing=0; // whether to print, and the base to print in
    int i,j;  // loop variables

    if(argc >=2)  // recall that first 'argument' is the program name
      number_of_bits = atoi(argv[1]);
    if(argc >=3)
      number_of_tests = atoi(argv[2]);
    if(argc >=4)
      printing=atoi(argv[3]);

    double start, finish, elapsed, elapsed_gmp = 0; //timing
    int num_correct = 0, num_wrong = 0; //correctness

    //allocate memory for printing to avoid memory leaks.  Use of
    //number_of_bits is enough to print in binary.
    char *x_str = (char *) malloc((1 + number_of_bits)*sizeof(char));
    char *y_str = (char *) malloc((1 + number_of_bits)*sizeof(char));
    char *z_str = (char *) malloc((1 + 2*number_of_bits)*sizeof(char));
    char *res_str = (char *) malloc((1 + 2*number_of_bits)*sizeof(char));

      void *x_address;
      void *y_address;
      size_t *x_size = malloc(sizeof(size_t));
      size_t *y_size = malloc(sizeof(size_t));
      size_t *z_size = malloc(sizeof(size_t));

    for(j = 0; j < number_of_tests; j++){

      if (rand() %2) {  // half the time the numbers are about the same size
        xbits = number_of_bits;
        ybits = number_of_bits;
      } else if (rand() % 2) { // 1/4 the time y is randomly smaller
        xbits= number_of_bits;
        ybits = rand() % number_of_bits;
      } else {  // else x is smaller
        ybits= number_of_bits;
        xbits = rand() % number_of_bits;
      }
      mpz_ui_pow_ui(xsize_z, 2, xbits); //numsize = 2^numbits
      mpz_ui_pow_ui(ysize_z, 2, ybits); //numsize = 2^numbits
      mpz_urandomm(x, randstate, xsize_z); //actually makes x and y
      mpz_urandomm(y, randstate, ysize_z);
      if (printing)
        printf("x %s\ny %s\n",mpz_get_str(x_str,printing,x),
            mpz_get_str(y_str,printing,y));

      x_address = mpz_export(NULL, x_size, ORDER, WORDBYTES, ENDIAN, NAILS, x);
      y_address = mpz_export(NULL, y_size, ORDER, WORDBYTES, ENDIAN, NAILS, y);

      void *z_address = malloc((*x_size + *y_size)*WORDBYTES);

      start = mytime();
      Product32(x_address, y_address, z_address, *x_size, *y_size, z_size);
      finish = mytime();
      elapsed += finish-start;
//      printf("%g %g %g\n",start, finish, elapsed);

      mpz_t z;
      mpz_init(z);

      mpz_import(z, *z_size, ORDER, WORDBYTES, ENDIAN, NAILS, z_address);
      if (printing)
        printf("code x*y %s\n", mpz_get_str(z_str,printing,z));

      mpz_t result;
      mpz_init(result);
      start = mytime();
      mpz_mul(result,x,y);
      finish = mytime();
      elapsed_gmp += finish-start;
      if (printing)
        printf("GMP  x*y %s\n", mpz_get_str(res_str,printing,result));

      if(mpz_cmp(result,z) == 0)
        num_correct ++;
      else
        num_wrong ++;

      free(x_address);
      free(y_address);
      free(z_address);
    }

    printf("============================================================\n");
    printf("\n");
    printf("Number of bits per number:        %d\n", number_of_bits);
    printf("Number of multiplications done:   %d\n", number_of_tests);
    printf("Number of correct multiplication: %d\n", num_correct);
    printf("Number of wrong multiplication:   %d\n", num_wrong);
    printf("Total seconds elapsed (code):     %lf\n", ((double) (elapsed)));
    printf("Total seconds elapsed (GMP):      %lf\n", ((double)
          (elapsed_gmp)));
    printf("Total time ratio (code/GMP):      %lf \n",
        elapsed/elapsed_gmp);
    printf("------------------------------------------------------------\n");
    printf("Number of multiplications using A18base:  %d\n",n18b);
    printf("Number of multiplications using A18large: %d\n",n18l);
    printf("Number of multiplications using A52:      %d\n",n52);
    printf("\n");
    printf("============================================================\n");
    return 0;
}
